# -*- coding: utf-8 -*-
import json
import math

from . import base_control
from . import q_adjust_model
from ..solver import lp_solve

class voltage_control(base_control.base_control):

    def __init__(self, micro_grid, req):
        super(voltage_control, self).__init__()

        self.micro_grid = micro_grid

        self.ctrl_id = None

        self.pf_boundary = self.micro_grid.pf_boundary

        self.lp_solve = lp_solve.lp_solve()

    def adjust_ref_voltage(self, Vl_ref, Vl_measure):

        return 0.5*(Vl_ref + Vl_measure)

    # Compute reference voltage
    def calculate_reference_voltage(self, Q_min, Q_max, Vnorm, Q, Vl_measure):
        #Vl_ref = 0.95*Vnorm + 0.1*Vnorm*(Q - Q_max)/(Q_min - Q_max)
        if Vl_measure < 0.95*Vnorm: #0.95 and 1.05 can be user defined.
            Vl_ref = 0.95 * Vnorm
        elif Vl_measure > 1.05*Vnorm:
            Vl_ref = 1.05 * Vnorm
        else:
            Vl_ref = Vl_measure
        return Vl_ref

    # Compute the total current of microgrid
    def calculate_total_current(self, V_S, V_L, jX):
        I_L = (V_S - V_L)/jX
        return I_L

    # Compute reference current at load node
    def calculate_reference_current(self, V_S, V_L_Ref, jX):
        I_L_Ref = (V_S - V_L_Ref)/jX
        return I_L_Ref

    # Compute shift of reactive power from reference at load node
    def calculate_var_shift(self, V_L, V_L_Ref, X):
        var_shift = (V_L_Ref - V_L) / X
        return var_shift

    # Compute total power at load node
    def calculate_power(self, V_p, V_q, I_p, I_q):
        return V_p*I_p - V_q*I_q, V_q*I_p + V_p*I_q

    # Voltage adjust by P
    def calculate_p_shift(self, X, Vl_p, Vl_q, Vl_ref, delta_I_q):
        temp = Vl_ref*Vl_ref - math.pow(Vl_p + X*delta_I_q,2)

        # temp should be positive
        if temp > 0:
            p_shift = (Vl_q + math.sqrt(temp))/X
            p_shift1 = (Vl_q - math.sqrt(temp))/X
            if abs(p_shift) > abs(p_shift1):
                p_shift = p_shift1
        else:
            p_shift = Vl_q / X

        return Vl_ref*p_shift

    # Voltage adjust loop
    def voltage_adjust_loop(self,Vl_obs, Vl_ref):

        # Step 3: Compute Q adjustment
        delta_I_q = self.calculate_var_shift(Vl_obs, Vl_ref, self.micro_grid.jX)

        delta_q = Vl_ref * delta_I_q

        print (delta_q)

        # Step 4:Assign var_delta to different device
        # Get the Q adjusting boundary
        QDecreaseLimit, QIncreaseLimit = self.micro_grid.getQAdjustBound()
        tempQAdjustList = [0] * self.micro_grid.n_active_devices
        tempPAdjustList = [0] * self.micro_grid.n_active_devices

        if (delta_q < 0 and delta_q >= QDecreaseLimit) or (
                delta_q > 0 and delta_q <= QIncreaseLimit):
            if delta_q < 0: #Reduce Q
                q_model = q_adjust_model.q_adjust_model(self.micro_grid.get_lower_q_abs(), -delta_q)

                tempQAdjustList = -1*self.lp_solve.run(q_model.get_model())
            else:
                q_model = q_adjust_model.q_adjust_model(self.micro_grid.get_upper_q_abs(), delta_q)

                tempQAdjustList = self.lp_solve.run(q_model.get_model())

        else:
            if delta_q < 0:
                tempQAdjustList = self.micro_grid.get_lower_q_boundary()
            elif delta_q > 0:
                tempQAdjustList = self.micro_grid.get_upper_q_boundary()

        # Step 5: Compute P adjustment
        tempPAdjustList = self.micro_grid.calc_p_adjustment(tempQAdjustList)

        return tempPAdjustList, tempQAdjustList


    # Voltage adjust by P
    def voltage_p_control(self, X, R, Vl_p, Vl_q, Vl_ref, delta_I_q):
        temp = Vl_ref*Vl_ref - math.pow(Vl_p + X*delta_I_q,2)

        # temp should be positive
        a = X*X + R*R 
        b = -2*Vl_p*R - 2*Vl_q*X
        c = Vl_p*Vl_p + Vl_q*Vl_q

        temp = b*b - 4*a*c
        if temp > 0:
            p_shift = (-1*b + math.sqrt(temp))/(2*a)
            p_shift1 = (-1*b - math.sqrt(temp))/(2*a)
            if abs(p_shift) > abs(p_shift1):
                p_shift = p_shift1
        else:
            p_shift = 0 #should not happen

        control_command = []
        #for i in range(0, len(self.micro_grid.device_list)):
	#    if self.micro_grid.device_list[i].dev_id == 2:
        device_command = dict()
        device_command['device_id'] = "2"#self.micro_grid.device_list[i].dev_id
        device_command['q_value'] = None
        device_command['p_value'] = 100#100#Vl_ref*p_shift
        device_command['type'] = "Realtime"

        control_command.append(device_command)

        commands = {"10401":control_command}

        return commands

    # The steps of voltage control
    def voltage_q_control(self, Vl_p, Vl_q, Il_p, Il_q):
        # Vl_p, Vl_q, Il_p, Il_q are measurement

        # Step 1: Compute P and Q
        Pl, Ql = self.calculate_power(Vl_p, Vl_q, Il_p, Il_q)

        Vl_measure = math.sqrt(Vl_p * Vl_p + Vl_q * Vl_q)

        # Step 2: Compute V_ref according to reference curve
        Q_min, Q_max = self.micro_grid.getQBound()

        Vl_ref = self.calculate_reference_voltage(Q_min, Q_max, self.micro_grid.Vnorm, Ql, Vl_measure)

        print(Vl_ref)

        # Step 3: Update device status
        if Vl_ref == Vl_measure: # Do nothing :add a tolerance
            tempQAdjustList = [0] * self.micro_grid.n_active_devices
            tempPAdjustList = [0] * self.micro_grid.n_active_devices
        else:
            while True:
                # Step 4: try to assign Q changes
                tempPAdjustList, tempQAdjustList = self.voltage_adjust_loop(Vl_measure, Vl_ref)

                # Step 5: Compute power factor
                pf = self.micro_grid.calc_power_factor()

                if pf < self.pf_boundary:
                    # Adjust voltage reference value
                    Vl_ref = self.adjust_ref_voltage(Vl_measure, Vl_ref)
                else:
                    break

            # Step 6: adjust P if change Q does not work
            #if sum(tempQAdjustList) == 0:


        # Step 7: Return a jason object
        #control_command_str = self.micro_grid.gen_control_command()

        control_command = []
        for i in range(0, len(self.micro_grid.device_list)):
            device_command = dict()
            device_command['device_id'] = self.micro_grid.device_list[i].dev_id
            device_command['p_value'] = tempQAdjustList[i]
            device_command['q_value'] = None
            device_command['type'] = "Realtime"

            control_command.append(device_command)

        commands = {"10401":control_command}
        #control_command_str = json.dumps(control_command)

        return commands
